package com.zstk.frame.config.interceptor;

import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.zstk.frame.util.ZStringUtil;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 * 处理模糊匹配时参数特殊字符转义问题
 * </p>
 *
 * @author zxt
 * @since 2022/7/4 13:13
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
                                                                         Object.class,
                                                                         RowBounds.class,
                                                                         ResultHandler.class})})
@Component
@Slf4j
@ConditionalOnClass(value = Interceptor.class)
public class RegexpInterceptor implements Interceptor {
    @Value("${spring.datasource.dbtype}")
    private String dbType;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //先暂时 只处理mysql oracle   不支持多数据源
        if (MybatisSqlConstant.MYSQL.equals(dbType) || MybatisSqlConstant.ORACLE.equals(dbType)) {
            Object[] args = invocation.getArgs();
            MappedStatement statement = (MappedStatement) args[0];
            Object parameterObject = args[1];
            BoundSql boundSql = statement.getBoundSql(parameterObject);
            String sql = boundSql.getSql();
            if (sql.toLowerCase().contains("regexp")) {
                if (parameterObject instanceof Map) {
                    this.transferRegexpSql(sql, (Map<Object, Object>) parameterObject, boundSql);
                }
            }
        }
        return invocation.proceed();
    }

    /**
     * 处理级联属性
     *
     * @param field     级联字段名
     * @param parameter 参数Map对象
     */
    private void resolveCascadeObj(String field, Map parameter) {
        int index = field.indexOf(MybatisSqlConstant.MYBATIS_PLUS_WRAPPER_SEPARATOR);
        Object param = parameter.get(field.substring(0, index));
        if (param == null) {
            return;
        }
        this.resolveObj(field.substring(index + 1), param);
    }

    private void resolveObj(String field, Object parameter) {
        if (parameter == null || StringUtils.isBlank(field)) {
            return;
        }
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(field, parameter.getClass());
            Method readMethod = descriptor.getReadMethod();
            Object param = readMethod.invoke(parameter);
            if (!param.toString().contains(MybatisSqlConstant.ESCAPE_CHAR)) {
                Method setMethod = descriptor.getWriteMethod();
                setMethod.invoke(parameter, ZStringUtil.escapeChar(param.toString()));
            } else if (MybatisSqlConstant.cascade(field)) {
                int index = field.indexOf(MybatisSqlConstant.MYBATIS_PLUS_WRAPPER_SEPARATOR) + 1;
                this.resolveObj(field.substring(index), param);
            }
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            log.error("反射 {} 的 {} get/set方法出现异常", parameter, field, e);
        }
    }

    /**
     * 处理regexp 模糊字段对应参数
     */
    private void transferRegexpSql(String sql, Map<Object, Object> parameterObject, BoundSql boundSql) {
        String[] params = sql.split(MybatisSqlConstant.SQL_PLACEHOLDER_REGEX);
        Set<String> fields = new HashSet<>();
        for (int i = 0; i < params.length; i++) {
            if (params[i].toLowerCase().contains("regexp")) {
                String field = boundSql.getParameterMappings().get(i).getProperty();
                fields.add(field);
            }
        }
        fields.forEach(v -> {
            if (MybatisSqlConstant.hasWrapper(v)) {
                AbstractWrapper wrapper =
                        (AbstractWrapper) parameterObject.get(MybatisSqlConstant.MYBATIS_PLUS_WRAPPER_KEY);
                Map<Object, Object> mapParam = wrapper.getParamNameValuePairs();
                String[] keys = v.split(MybatisSqlConstant.MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX);
                // ew.paramNameValuePairs.param1，截取字符串之后，获取第三个，即为参数名
                String paramName = keys[2];
                String mapKey = String.format("%s.%s", MybatisSqlConstant.REPLACED_LIKE_KEYWORD_MARK, paramName);
                String value = (String) mapParam.get(paramName);
                if (!value.contains(MybatisSqlConstant.ESCAPE_CHAR)) {
                    parameterObject.put(mapKey, ZStringUtil.escapeChar(value));
                }
            } else {
                if (MybatisSqlConstant.cascade(v)) {
                    this.resolveCascadeObj(v, parameterObject);
                    return;
                }
                String param = (String) parameterObject.get(v);
                if (!param.contains(MybatisSqlConstant.ESCAPE_CHAR)) {
                    parameterObject.put(v, ZStringUtil.escapeChar(param));
                }
            }


        });

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties arg0) {

    }
}
